﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net.Sockets;
using System.Net;
using rkShared.Serialization;
using rkShared.BEncode;
using System.Reflection;
using rkShared.Logging;
using System.IO.Compression;

namespace rkShared.Net
{
    [Serializable]
    public class NetMessage : IBinarySerializable
    {
        public MessageType MessageType { get; set; }
        public object Data { get; set; }
        public Guid ClientID { get; set; }

        /// <summary>
        /// Used to acquire lock before writing to udp...
        /// </summary>
        private static object sm_udpSyncObject;

        static NetMessage()
        {
            sm_udpSyncObject = new object();
        }
        public NetMessage()
        {
        }
        private byte[] GetBytes()
        {
            BEncodedDictionary _dict = new BEncodedDictionary();
            this.SerializeToDictionary(_dict);
            MemoryStream _stream = new MemoryStream();
            //GZipStream _zipStream = new GZipStream(_stream, CompressionMode.Compress);
            BinaryWriter _writer = new BinaryWriter(_stream);
            _dict.Encode(_writer);
            _writer.Flush();

            byte[] _result = _stream.ToArray();
            _stream.Close();
            return _result;
        }
        /// <summary>
        /// Send this NetMessage object over UDP Socket
        /// </summary>
        /// <param name="s"></param>
        /// <param name="ipAddress"></param>
        /// <param name="port"></param>
        public void SendOverUDP(Socket s, string ipAddress, int port)
        {
            NetworkLogger.Log.InfoFormat("SEND OVER UDP STARTED - {0}:{1}", ipAddress, port);
            byte[] _this = this.GetBytes();

            NetworkLogger.Log.InfoFormat("SENDING {0} bytes over UDP to {1}:{2}", _this.Length, ipAddress, port);

            lock (sm_udpSyncObject)
            {
                NetworkLogger.Log.InfoFormat("ACQUIERED UDP LOCK to {0}:{1}", ipAddress, port);
                //Write the length of the serialized data...
                s.SendTo(BitConverter.GetBytes(_this.Length), new IPEndPoint(IPAddress.Parse(ipAddress), port));
                //Write the data...
                s.SendTo(_this, new IPEndPoint(IPAddress.Parse(ipAddress), port));
                NetworkLogger.Log.InfoFormat("RELEASING UDP LOCK to {0}:{1}", ipAddress, port);
            }

            NetworkLogger.Log.InfoFormat("EXITING SEND OVER UDP to {0}:{1}", ipAddress, port);
        }
        /// <summary>
        /// Send this NetMessage object over UDP Socket
        /// </summary>
        /// <param name="s"></param>
        /// <param name="ipAddress"></param>
        /// <param name="port"></param>
        public void SendOverTCP(Socket s)
        {
            if (s.Connected)
            {
                NetworkLogger.Log.InfoFormat("SEND OVER TCP STARTED - {0}:{1}",
                    (s.RemoteEndPoint as IPEndPoint).Address, (s.RemoteEndPoint as IPEndPoint).Port);

                byte[] _this = this.GetBytes();
                NetworkLogger.Log.InfoFormat("SENDING {0} bytes over TCP to {1}:{2}", _this.Length,
                    (s.RemoteEndPoint as IPEndPoint).Address, (s.RemoteEndPoint as IPEndPoint).Port);

                //Write the length of the serialized data...
                s.Send(BitConverter.GetBytes(_this.Length));
                //Write the data...
                s.Send(_this);
                NetworkLogger.Log.InfoFormat("EXITING SEND OVER TCP to {0}:{1}",
                    (s.RemoteEndPoint as IPEndPoint).Address, (s.RemoteEndPoint as IPEndPoint).Port);
            }
        }
        #region IBinarySerializable Members

        public void SerializeToDictionary(rkShared.BEncode.BEncodedDictionary dictionary)
        {
            NetworkLogger.Log.InfoFormat("STARTED SERIALIZING NETMESSAGE");
            NetworkLogger.Log.InfoFormat("MESSAGE TYPE IS {0}\r\n DATA TYPE IS {1}", this.MessageType,
                this.Data.GetType().Name);

            if (this.Data != null)
            {
                if (this.Data is IBElement)
                {
                    dictionary.Add("b", 1); //is IBElement = true...
                    dictionary.Add("d", this.Data as IBElement);
                }
                else if (this.Data is IBinarySerializable)
                {
                    dictionary.Add("b", 0);//is IBelement = false...
                    dictionary.Add("dt", this.Data.GetType().FullName); //dt = DataType name, for deserializing...

                    IBinarySerializable _data = this.Data as IBinarySerializable;
                    BEncodedDictionary _dataDict = new BEncodedDictionary();
                    _data.SerializeToDictionary(_dataDict);

                    dictionary.Add("d", _dataDict);
                }
                else
                {
                    string _msg = string.Format("Cannot serialize datatype \"{0}\"", this.Data.GetType().Name);
                    Exception ex = new InvalidOperationException(_msg);
                    NetworkLogger.Log.Error(_msg, ex);
                    throw ex;
                }
            }
            else
                dictionary.Add("n", new BEncodedInt(1));    //data is null...

            dictionary.Add("mt", (int)this.MessageType);
            dictionary.Add("c", this.ClientID.ToString());

            NetworkLogger.Log.InfoFormat("EXITING SERIALIZING NETMESSAGE");
        }
        public void DeserializeFromDictionary(rkShared.BEncode.BEncodedDictionary dict)
        {
            NetworkLogger.Log.InfoFormat("STARTED DESERIALIZING NETMESSAGE");

            this.ClientID = new Guid(dict.GetString("c"));
            this.MessageType = (MessageType)(dict.GetInt("mt"));

            bool _isDataNull = dict.ContainsKey("n");
            if (!_isDataNull)
            {

                int _isBElement = dict.GetInt("b");
                NetworkLogger.Log.InfoFormat("MESSAGETYPE WAS {0}", _isBElement == 1 ? "BEncodedElement" : "IBinarySerializable");

                if (_isBElement == 1)
                {
                    // JUST get the data from dictionary..
                    this.Data = dict["d"];
                }
                else if (_isBElement == 0)
                {
                    string _typeName = dict.GetString("dt");
                    NetworkLogger.Log.InfoFormat("DESERIALING TYPE \"{0}\"", _typeName);

                    ConstructorInfo _ci = Type.GetType(_typeName).GetConstructor(Type.EmptyTypes);
                    IBinarySerializable _data = _ci.Invoke(null) as IBinarySerializable;

                    BEncodedDictionary _dataDict = dict["d"] as BEncodedDictionary;
                    _data.DeserializeFromDictionary(_dataDict);

                    this.Data = _data;
                }
            }
            NetworkLogger.Log.InfoFormat("EXITING DESERIALIZING NETMESSAGE");
        }

        #endregion
    }
    public delegate void MessageReceivedEventHandler(ClientInfo client, NetMessage msg);

    public delegate void ClientRegisteredEventHandler(ClientInfo cliInfo);

	public delegate void ClientEnteredGameEventHandler(ClientInfo cliInfo,NetMessage msg);
}
